1 /* 2 Copyright: Marcelo S. N. Mancini (Hipreme|MrcSnm), 2018 - 2021 3 License: [https://creativecommons.org/licenses/by/4.0/|CC BY-4.0 License]. 4 Authors: Marcelo S. N. Mancini 5 6 Copyright Marcelo S. N. Mancini 2018 - 2021. 7 Distributed under the CC BY-4.0 License. 8 (See accompanying file LICENSE.txt or copy at 9 https://creativecommons.org/licenses/by/4.0/ 10 */ 11 module hip.assetmanager; 12 import hip.util.data_structures: Node; 13 import hip.util.reflection; 14 import hip.error.handler; 15 import hip.console.log : hiplog; 16 17 18 private string buildConstantsFromFolderTree(string code, Node!string node, int depth = 0) 19 { 20 import hip.util.path; 21 import hip.util.string; 22 if(node.hasChildren && node.data.extension == "") 23 { 24 code = "\t".repeat(depth)~"class " ~ node.data~ "\n"~"\t".repeat(depth)~"{\n"; 25 foreach(child; node.children) 26 { 27 code~= "\t".repeat(depth)~buildConstantsFromFolderTree(code, child, depth+1)~"\n"; 28 } 29 code~="\n"~"\t".repeat(depth)~"}\n"; 30 } 31 else if(!node.hasChildren && node.data.extension != "") 32 { 33 string propName = node.data[0..$-(node.data.extension.length+1)]; 34 return "\tpublic static enum "~propName~" = `"~node.buildPath~"`;"; 35 } 36 return code; 37 } 38 39 mixin template HipAssetsGenerateEnum(string filePath) 40 { 41 import hip.util.path; 42 mixin(buildConstantsFromFolderTree("", buildFolderTree(import(filePath).split('\n')))); 43 } 44 45 46 import hip.util.system; 47 import hip.concurrency.thread; 48 import hip.concurrency.mutex; 49 public import hip.assets.image; 50 public import hip.audio.clip; 51 public import hip.assets.texture; 52 public import hip.assets.tilemap; 53 public import hip.font.bmfont; 54 public import hip.font.ttf; 55 public import hip.assets.csv; 56 public import hip.assets.jsonc; 57 public import hip.data.ini; 58 public import hip.api.data.commons; 59 public import hip.assets.textureatlas; 60 public import hip.util.data_structures; 61 public import hip.api.data.font; 62 public import hip.api.input.inputmap; 63 public import hip.assets.inputmap; 64 65 66 67 class HipAssetManager 68 { 69 import hip.config.opts; 70 71 package __gshared HipWorkerPool workerPool; 72 __gshared float currentTime; 73 74 /** 75 * Due to a bug in the D Runtime, I can't use TypeInfo over the dll boundaries. 76 * `is` is being used instead of opEquals 77 */ 78 protected __gshared IHipAssetLoadTask delegate(string path, const(ubyte)[] extraData, string file = __FILE__, size_t line = __LINE__)[string] typedAssetFactory; 79 //Caching 80 protected __gshared HipAsset[string] assets; 81 protected __gshared IHipAssetLoadTask[string] loadCache; 82 83 //Thread Communication 84 protected __gshared IHipAssetLoadTask[] loadQueue; 85 protected __gshared void delegate(HipAsset)[][IHipAssetLoadTask] completeHandlers; 86 protected __gshared void delegate()[] onEveryLoadFinished; 87 88 89 public static void initialize() 90 { 91 import hip.loaders.audio; 92 import hip.loaders.fonts; 93 import hip.loaders.image; 94 import hip.loaders.text; 95 import hip.loaders.texture_atlas; 96 import hip.loaders.texture; 97 import hip.loaders.tilemap; 98 import hip.loaders.tileset; 99 100 workerPool = new HipWorkerPool(HIP_ASSETMANAGER_WORKER_POOL); 101 typedAssetFactory = [ 102 typeid(IHipAudioClip).toString : (string path, const(ubyte)[] extraData, string f, size_t l)=> new HipAudioLoadTask(path,path, null, extraData, f, l), 103 typeid(IHipFont).toString : (string path, const(ubyte)[] extraData, string f, size_t l) 104 { 105 import hip.util.path; 106 hiplog("Trying to load the font ", path, "EXT: ", path.extension); 107 switch(path.extension) 108 { 109 case "bmfont": 110 case "fnt": 111 return new HipBMFontLoadTask(path, path, null, 48, extraData, f, l); 112 case "ttf": 113 case "otf": 114 return new HipTTFFontLoadTask(path, path, null, 48, extraData, f, l); 115 default: return null; 116 } 117 }, 118 typeid(IImage).toString : (string path, const(ubyte)[] extraData, string f, size_t l) => new HipImageLoadTask(path,path,null, extraData, f,l), 119 typeid(string).toString : (string path, const(ubyte)[] extraData, string f, size_t l) => new HipFileLoadTask(path,path,null, extraData, f,l), 120 typeid(IHipIniFile).toString : (string path, const(ubyte)[] extraData, string f, size_t l) => new HipINILoadTask(path,path,null,extraData,f,l), 121 typeid(IHipCSV).toString : (string path, const(ubyte)[] extraData, string f, size_t l) => new HipCSVLoadTask(path,path,null, extraData, f,l), 122 typeid(IHipJSONC).toString : (string path, const(ubyte)[] extraData, string f, size_t l) => new HipJSONCLoadTask(path,path,null, extraData, f,l), 123 typeid(IHipTextureAtlas).toString : (string path, const(ubyte)[] extraData, string f, size_t l) => new HipTextureAtlasLoadTask(path,path,null, ":IGNORE", extraData, f,l), 124 typeid(IHipTexture).toString : (string path, const(ubyte)[] extraData, string f, size_t l) => new HipTextureLoadTask(path,path,null, extraData, f,l), 125 typeid(IHipTilemap).toString : (string path, const(ubyte)[] extraData, string f, size_t l) => new HipTilemapLoadTask(path,path,null, extraData, f,l), 126 typeid(IHipTileset).toString : (string path, const(ubyte)[] extraData, string f, size_t l) => new HipTilesetLoadTask(path,path,null, extraData, f,l), 127 typeid(IHipInputMap).toString : (string path, const(ubyte)[] extraData, string f, size_t l) => new HipInputMapLoadTask(path,path,null, extraData, f,l), 128 ]; 129 130 typedAssetFactory[typeid(HipAudioClip).toString] = typedAssetFactory[typeid(IHipAudioClip).toString]; 131 typedAssetFactory[typeid(HipFont).toString] = typedAssetFactory[typeid(IHipFont).toString]; 132 typedAssetFactory[typeid(Image).toString] = typedAssetFactory[typeid(IImage).toString]; 133 typedAssetFactory[typeid(HipINI).toString] = typedAssetFactory[typeid(IHipIniFile).toString]; 134 typedAssetFactory[typeid(HipJSONC).toString] = typedAssetFactory[typeid(IHipJSONC).toString]; 135 typedAssetFactory[typeid(HipTextureAtlas).toString] = typedAssetFactory[typeid(IHipTextureAtlas).toString]; 136 typedAssetFactory[typeid(HipTexture).toString] = typedAssetFactory[typeid(IHipTexture).toString]; 137 typedAssetFactory[typeid(HipTileset).toString] = typedAssetFactory[typeid(IHipTileset).toString]; 138 typedAssetFactory[typeid(HipInputMap).toString] = typedAssetFactory[typeid(IHipInputMap).toString]; 139 140 141 } 142 143 static bool isAsync = HipConcurrency; 144 145 static if(HipConcurrency) 146 { 147 import core.sync.mutex; 148 } 149 150 151 @ExportD static HipAsset getAsset(string name) 152 { 153 if(HipAsset* asset = name in assets) 154 return *asset; 155 return null; 156 } 157 158 @ExportD static string getStringAsset(string name) 159 { 160 HipAsset asset = getAsset(name); 161 if(asset !is null) 162 { 163 HipFileAsset fA = cast(HipFileAsset)asset; 164 assert(fA !is null, "Asset fetched is not a file asset."); 165 return fA.getText; 166 } 167 else 168 return null; 169 } 170 171 static pragma(inline, true) T get(T)(string name) {return cast(T)getAsset(name);} 172 static pragma(inline, true) T get(T : string)(string name) {return getStringAsset(name);} 173 174 ///Returns whether asset manager is loading anything 175 @ExportD static bool isLoading(string file = __FILE__, uint line = __LINE__) 176 { 177 return loadQueue.length != 0; 178 } 179 ///Returns whether asset manager is loading anything 180 @ExportD static int getAssetsToLoadCount() 181 { 182 return cast(int)loadQueue.length; 183 } 184 185 ///Stops the code from running and awaits asset manager to finish loading 186 @ExportD static void awaitLoad() 187 { 188 workerPool.await; 189 update(); 190 } 191 192 static void awaitTask(IHipAssetLoadTask task) 193 { 194 static if(HipConcurrency) 195 { 196 import hip.asset_manager.load_task; 197 import core.sync.semaphore; 198 HipAssetLoadTask lTask = cast(HipAssetLoadTask)task; 199 auto semaphore = new Semaphore(0); 200 lTask.worker.pushTask("Await Single", () 201 { 202 semaphore.notify; 203 }); 204 semaphore.wait; 205 destroy(semaphore); 206 update(); 207 } 208 } 209 210 package static HipWorkerThread loadWorker(string taskName, void delegate() loadFn, void delegate(string taskName) onFinish = null, bool onMainThread = false) 211 { 212 //TODO: Make it don't use at all worker and threads. 213 //? Maybe it is not actually needed, as it can be handled by version(HipConcurrency) 214 return workerPool.pushTask(taskName, loadFn, onFinish, onMainThread); 215 } 216 @ExportD static void registerAsset(TypeInfo tID, IHipAssetLoadTask delegate(string path, const(ubyte)[] extraData, string file, size_t line) assetFactory) 217 { 218 if(tID.toString in typedAssetFactory) 219 { 220 ErrorHandler.showErrorMessage("Asset already registered:", tID.toString); 221 throw new Exception("Attempt to register twice the same type."); 222 } 223 typedAssetFactory[tID.toString] = assetFactory; 224 } 225 226 @ExportD static IHipAssetLoadTask loadAsset(TypeInfo tID, string path, const(ubyte)[] extraData = null, string file = __FILE__, size_t line = __LINE__) 227 { 228 IHipAssetLoadTask* cached = path in loadCache; 229 if(cached) 230 return *cached; 231 auto assetFactory = tID.toString in typedAssetFactory; 232 if(!assetFactory) 233 { 234 import hip.util.string; 235 String s = String(); 236 ErrorHandler.showErrorMessage("Asset type was not registered in AssetManager:", tID.toString); 237 238 foreach(type, factory; typedAssetFactory) 239 s~= "\n\t- "~type.toString; 240 241 ErrorHandler.showErrorMessage("Registered Types: ", s.toString); 242 243 throw new Exception("Please register the type first."); 244 } 245 246 IHipAssetLoadTask task = (*assetFactory)(path, extraData,file, line); 247 loadQueue~= task; 248 return task; 249 } 250 251 @ExportD static IHipTilemap createTilemap(uint width, uint height, uint tileWidth, uint tileHeight) 252 { 253 return new HipTilemap(width, height, tileWidth, tileHeight); 254 } 255 @ExportD static IHipTileset tilesetFromAtlas(IHipTextureAtlas atlas){return HipTileset.fromAtlas(cast(HipTextureAtlas)atlas);} 256 @ExportD static IHipTileset tilesetFromSpritesheet(Array2D_GC!IHipTextureRegion sp){return HipTileset.fromSpritesheet(sp);} 257 258 static void addOnCompleteHandler(IHipAssetLoadTask task, void delegate(HipAsset) onComplete) 259 { 260 import hip.asset_manager.load_task; 261 if(task.asset !is null) 262 onComplete(task.asset); 263 else 264 { 265 hiplog("Added a complete handler for ", (cast(HipAssetLoadTask)task).name); 266 completeHandlers[cast(HipAssetLoadTask)task]~= onComplete; 267 } 268 } 269 270 static void addOnLoadingFinish(void delegate() onFinish) 271 { 272 onEveryLoadFinished~= onFinish; 273 } 274 275 /** 276 * This function is responsible for calling worker's onTaskFinish on the main thread if it has one. 277 * After that, it will execute any deferred task to the AssetManager, such as setting a HipSprite or HipFont asset. 278 */ 279 static void update() 280 { 281 import hip.util.array:remove; 282 import hip.asset_manager.load_task; 283 workerPool.startWorking(); 284 for(int i = 0; i < loadQueue.length; i++) 285 { 286 IHipAssetLoadTask task = loadQueue[i]; 287 HipAssetLoadTask lTask = cast(HipAssetLoadTask)task; 288 task.update(); 289 final switch(task.result) with(HipAssetResult) 290 { 291 case cantLoad: 292 ErrorHandler.showWarningMessage("Could not load task: "~lTask.name, " Error: "~ lTask.error); 293 loadQueue.remove(task); 294 break; 295 case loading, waiting: 296 break; 297 case mainThreadLoading: 298 break; 299 case loaded: 300 if(auto handlers = task in completeHandlers) 301 { 302 //Subject to a logger 303 hiplog(lTask.name, " executing handlers"); 304 foreach(handler; *handlers) 305 { 306 handler(lTask._asset); 307 } 308 handlers.length = 0; 309 completeHandlers.remove(task); 310 } 311 loadQueue.remove(task); 312 // loadCache[task.] 313 i--; 314 break; 315 } 316 } 317 if(loadQueue.length == 0 && onEveryLoadFinished.length) 318 { 319 foreach(handler; onEveryLoadFinished) 320 handler(); 321 onEveryLoadFinished.length = 0; 322 } 323 workerPool.pollFinished(); 324 } 325 326 /** 327 * Cleans everything up. Puts AssetManager in an invalid state 328 */ 329 static void dispose() 330 { 331 import hip.error.handler; 332 workerPool.dispose(); 333 foreach(HipAsset asset; assets.byValue) 334 asset.dispose(); 335 destroy(assets); 336 destroy(loadQueue); 337 destroy(workerPool); 338 } 339 }